package cn.cosmosx.base.util.codec;

import lombok.extern.slf4j.Slf4j;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;

/**
 * JAXB 实现bean与xml之间相互转换
 * <p>
 * 关键注解 {@linkplain javax.xml.bind.annotation.XmlRootElement}
 * {@linkplain javax.xml.bind.annotation.XmlElement}
 * {@linkplain javax.xml.bind.annotation.XmlTransient}
 * {@linkplain javax.xml.bind.annotation.XmlElementRef}
 * {@linkplain javax.xml.bind.annotation.XmlElementWrapper}
 *
 * <pre>
 * {@code
 * 注：
 * JAXBContext类：是应用的入口，用于管理XML/Java绑定信息。
 * Marshaller接口：将Java对象序列化为XML数据。
 * Unmarshaller接口：将XML数据反序列化为Java对象。
 * @XmlRootElement：将Java类或枚举类型映射到XML元素，用在Java类上，用于标注该类是xml的一个根节点。
 * @XmlElement：将Java类的一个属性映射到与属性同名的一个XML元素。通常与@XmlTransient搭配使用。
 * @XmlTransient：通常与 @XmlElement 须搭配使用的。@XmlElement用在属性上，用于指定生成xml的节点名，@XmlTransient用在对应的getter方法上，起到关联的作用。
 * @XmlElementWrapper ：对于数组或集合（即包含多个元素的成员变量），生成一个包装该数组或集合的XML元素（称为包装器）。
 * 通常配合XmlElement一起使用，XmlElementWrapper指定数组名，XmlElement指定生成xml的节点名。
 * @XmlElementRef：用在类属性的getter方法上（即该属性是一个JavaBean），并且该属性是某些子类的父类，起到引用的作用。
 * 同时标注得有@XmlElementRef的类属性，其子类上需要使用@XmlRootElement标注，否则转换异常，提示找不到具体的引用实现。
 * 另外，转换时，需要将其子类的class一起传递到JAXBContext上下文中，否则也无法转换。
 * @XmlAccessorOrder：控制JAXB 绑定类中属性和字段的排序。
 *
 * @XmlType：将Java类或枚举类型映射到XML模式类型
 * @XmlAccessorType(XmlAccessType.FIELD) ：控制字段或属性的序列化。FIELD表示JAXB将自动绑定Java类中的每个非静态的（static）、
 * 非瞬态的（由@XmlTransient标 注）字段到XML。还有XmlAccessType.PROPERTY和XmlAccessType.NONE。
 * @XmlJavaTypeAdapter：使用定制的适配器（即扩展抽象类XmlAdapter并覆盖marshal()和unmarshal()方法），以序列化Java类为XML。
 * @XmlAttribute，将Java类的一个属性映射到与属性同名的一个XML属性。
 * }
 * </pre>
 */
@Slf4j
public class JaxbXmlBeanUtil {
    private final static String DEFAULT_CHARSET = "UTF-8";

    /**
     * Java Bean 转 Xml
     *
     * @param bean         - Java Bean
     * @param inheritClazz - Java Bean中嵌套的类，且有继承关系的Java Class
     * @return - xml
     */
    public static String toXml(Object bean, Class<?>... inheritClazz) {
        return toXml(DEFAULT_CHARSET, bean, inheritClazz);
    }

    /**
     * Java Bean 转 Xml
     *
     * @param charset      - 编码
     * @param bean         - Java Bean
     * @param inheritClazz - Java Bean中嵌套的类，且有继承关系的Java Class
     * @return - xml
     */
    public static String toXml(String charset, Object bean, Class<?>... inheritClazz) {
        try {
            JAXBContext context = initContext(bean.getClass(), inheritClazz);
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            m.setProperty(Marshaller.JAXB_ENCODING, charset);
            // 是否去掉xml头
            m.setProperty(Marshaller.JAXB_FRAGMENT, false);
            StringWriter writer = new StringWriter();
            m.marshal(bean, writer);
            return writer.toString();
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Xml 转 Java Bean
     *
     * @param xml          - xml
     * @param beanClazz    - Java Bean Class
     * @param inheritClazz - Java Bean中嵌套的类，且有继承关系的Java Class
     * @return - bean
     */
    @SuppressWarnings("unchecked")
    public static <T> T fromXml(String xml, Class<T> beanClazz, Class<?>... inheritClazz) {
        try {
            JAXBContext context = initContext(beanClazz, inheritClazz);
            Unmarshaller um = context.createUnmarshaller();
            StringReader sr = new StringReader(xml);
            return (T) um.unmarshal(sr);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 初始化JAXBContext
     *
     * @param mainClazz    - 序列化或反序列化Class
     * @param inheritClazz - Java Bean中嵌套的类，且有继承关系的Java Class
     * @return - JAXBContext
     */
    private static JAXBContext initContext(Class<?> mainClazz, Class<?>... inheritClazz) throws JAXBException {
        JAXBContext context;
        if (inheritClazz != null) {
            Class<?>[] clazzArr = new Class[inheritClazz.length + 1];
            clazzArr[0] = mainClazz;
            System.arraycopy(inheritClazz, 0, clazzArr, 1, clazzArr.length - 1);
            context = JAXBContext.newInstance(clazzArr);
        } else {
            context = JAXBContext.newInstance(mainClazz);
        }
        return context;
    }

}

